Last active
November 14, 2023 08:34
-
-
Save gileli121/3b6396677e9f5345aa43b59badb7ecb3 to your computer and use it in GitHub Desktop.
MS Store - Solution to scenario that software changed from PAID & 30 Days trial to FREE & 30 Days trial
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
using System.Threading.Tasks; | |
using WindowTop.Helpers; | |
namespace WindowTop | |
{ | |
public class CacheEntity | |
{ | |
protected void SetValue<T>(T value, ref T propertyValue) | |
{ | |
if (value == null) | |
{ | |
try | |
{ | |
value = Activator.CreateInstance<T>(); | |
} | |
catch | |
{ | |
// ignored | |
} | |
} | |
if (value == null && propertyValue == null || (propertyValue != null && propertyValue.Equals(value))) | |
return; | |
propertyValue = value; | |
Cache.Save(); | |
} | |
} | |
public class Cache | |
{ | |
#region Struct info and helpers | |
static readonly AppLogger log = new AppLogger(); | |
const string CACHE_FILENAME = "cache.json"; | |
static string CacheFileName { get; set; } | |
public static Cache Values { get; private set; } | |
static bool disableAutoSave; | |
static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions() | |
{ | |
WriteIndented = true, | |
Converters = | |
{ | |
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) | |
} | |
}; | |
public static void DisableAutoSave() | |
{ | |
disableAutoSave = true; | |
} | |
public static void EnableAutoSave() | |
{ | |
disableAutoSave = false; | |
} | |
public static void Save() | |
{ | |
if (disableAutoSave) return; | |
if (Values == null) | |
Values = new Cache(); | |
File.WriteAllText(CacheFileName, | |
JsonSerializer.Serialize(Values, jsonSerializerOptions), Encoding.UTF8); | |
} | |
public static void Load() | |
{ | |
if (App.AppManager.IsInstalled) | |
{ | |
var configFolder = Path.Combine(Environment.GetFolderPath( | |
Environment.SpecialFolder.ApplicationData), App.AppName); | |
if (!Directory.Exists(configFolder)) | |
Directory.CreateDirectory(configFolder); | |
CacheFileName = Path.Combine(configFolder, CACHE_FILENAME); | |
} | |
else | |
{ | |
CacheFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, CACHE_FILENAME); | |
} | |
// Lines below are only for debugging. Remove it after done! | |
// if (File.Exists(CacheFileName)) | |
// File.Delete(CacheFileName); | |
// END | |
if (!File.Exists(CacheFileName)) | |
Save(); | |
DisableAutoSave(); | |
try | |
{ | |
Values = JsonSerializer.Deserialize<Cache>(File.ReadAllText(CacheFileName, Encoding.UTF8), | |
jsonSerializerOptions); | |
} | |
catch (Exception e) | |
{ | |
log.Fatal(e); | |
log.Info($"Trying to recovery by recreating the file {CACHE_FILENAME}"); | |
EnableAutoSave(); | |
File.Delete(CacheFileName); | |
Save(); | |
DisableAutoSave(); | |
Values = JsonSerializer.Deserialize<Cache>(File.ReadAllText(CacheFileName, Encoding.UTF8), | |
jsonSerializerOptions); | |
} | |
finally | |
{ | |
EnableAutoSave(); | |
} | |
} | |
#endregion | |
public UwpActivationCacheType UwpActivationCache { get; set; } = new UwpActivationCacheType(); | |
public class UwpActivationCacheType : CacheEntity | |
{ | |
bool isLicenseCached; | |
public bool | |
IsLicenseCached | |
{ | |
get => isLicenseCached; | |
set => SetValue(value, ref isLicenseCached); | |
} | |
bool isFullVersion; | |
public bool IsFullVersion | |
{ | |
get => isFullVersion; | |
set => SetValue(value, ref isFullVersion); | |
} | |
bool isTrialMode; | |
public bool IsTrialMode | |
{ | |
get => isTrialMode; | |
set => SetValue(value, ref isTrialMode); | |
} | |
int daysLeft; | |
public int DaysLeft | |
{ | |
get => daysLeft; | |
set => SetValue(value, ref daysLeft); | |
} | |
bool isTrialModeAvailable; | |
public bool IsTrialModeAvailable | |
{ | |
get => isTrialModeAvailable; | |
set => SetValue(value, ref isTrialModeAvailable); | |
} | |
DateTime? trialStartDate; | |
public DateTime? TrialStartDate | |
{ | |
get => trialStartDate; | |
set => SetValue(value, ref trialStartDate); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading.Tasks; | |
using System.Windows; | |
using WindowTop.DataUnits; | |
namespace WindowTop.Managers.ActivationManagers | |
{ | |
public class LoadActivationStateResult | |
{ | |
public bool IsActivationLoaded { get; set; } | |
public string LoadingFailureMessage { get; set; } | |
public bool IsActivationStateChanged { get; set; } | |
} | |
public interface IActivationManager | |
{ | |
bool IsLicenseCached { get; } | |
bool IsFullVersion { get; } | |
bool IsTrialMode { get; } | |
int DaysLeft { get; } | |
int MaxDaysLeft { get; } | |
bool IsFreeModeAllowed { get; } | |
bool IsTrialModeAvailable { get; } | |
bool IsProductKeyMethod { get; } | |
bool SupportUnActivation { get; } | |
bool SupportResetActivation { get; } | |
Action<bool> OnActivationStateChanged { get; set; } | |
Task<LoadActivationStateResult> LoadActivationStateAsync(); | |
void OpenPurchasePage(); | |
Task<Result<bool>> RequestSetTrialModeAsync(Window ownerWindow = null); // Should return false on error | |
bool UpdateTrailMode(); // Should return true if the trial property changed | |
Task<Result<bool>> RequestPurchaseAppAsync(Window ownerWindow = null); | |
void RequestActivateProductKey(string productKey, Action onActivated, | |
Action<string> onFailActivate, Action<string> onIllegalActivation); | |
void RequestCleanActivateProductKey(string productKey, Action onSuccess, Action<string> onFailure); | |
void RequestDeActivate(Action onUnActivated, Action<string> onUnActivateFailed); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace WindowTop.DataUnits | |
{ | |
public class Result<T> | |
{ | |
public T Value { get; set; } | |
public string ErrorMessage { get; set; } | |
public Exception Exception { get; set; } | |
public bool IsFailed => Exception != null || ErrorMessage != null; | |
public Result(Exception e, T value) : this(value) | |
{ | |
this.Exception = e; | |
this.ErrorMessage = e.Message; | |
} | |
public Result(string errorMessage, T value) : this(value) | |
{ | |
this.ErrorMessage = errorMessage; | |
} | |
public Result(T value) | |
{ | |
this.Value = value; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Text.Json; | |
using System.Threading.Tasks; | |
namespace WindowTop.Managers.ActivationManagers.UwpActivationManager.Models.SkuExtendedJsonData | |
{ | |
public class LegalText | |
{ | |
public string Copyright { get; set; } | |
public string CopyrightUri { get; set; } | |
public string PrivacyPolicy { get; set; } | |
public string PrivacyPolicyUri { get; set; } | |
public string Tou { get; set; } | |
public string TouUri { get; set; } | |
} | |
public class LocalizedProperty | |
{ | |
public string SkuDescription { get; set; } | |
public string SkuTitle { get; set; } | |
public string SkuButtonTitle { get; set; } | |
public List<object> SkuDisplayRank { get; set; } | |
public LegalText LegalText { get; set; } | |
public string Language { get; set; } | |
} | |
public class Properties | |
{ | |
public string FulfillmentType { get; set; } | |
public List<object> BundledSkus { get; set; } | |
public bool IsRepurchasable { get; set; } | |
public int SkuDisplayRank { get; set; } | |
public bool IsTrial { get; set; } | |
} | |
public class CollectionData | |
{ | |
public DateTime acquiredDate { get; set; } | |
public bool autoRenew { get; set; } | |
public List<object> additionalIds { get; set; } | |
public List<object> effectiveBeneficiaries { get; set; } | |
public DateTime endDate { get; set; } | |
public List<object> externalReferences { get; set; } | |
public List<object> fulfillmentData { get; set; } | |
public bool isCacheable { get; set; } | |
public string itemId { get; set; } | |
public string localTicketReference { get; set; } | |
public DateTime modifiedDate { get; set; } | |
public List<object> musicTracksInfo { get; set; } | |
public string orderId { get; set; } | |
public string orderLineItemId { get; set; } | |
public string ownershipType { get; set; } | |
public string productFamily { get; set; } | |
public string productId { get; set; } | |
public string productKind { get; set; } | |
public string productTitleId { get; set; } | |
public string purchasedCountry { get; set; } | |
public int quantity { get; set; } | |
public string skuId { get; set; } | |
public string skuType { get; set; } | |
public DateTime startDate { get; set; } | |
public string status { get; set; } | |
public List<object> tags { get; set; } | |
public string transactionId { get; set; } | |
} | |
public class Sku | |
{ | |
public List<LocalizedProperty> LocalizedProperties { get; set; } | |
public Properties Properties { get; set; } | |
public string SkuId { get; set; } | |
public object RecurrencePolicy { get; set; } | |
public CollectionData CollectionData { get; set; } | |
} | |
public class Conditions | |
{ | |
public DateTime EndDate { get; set; } | |
} | |
public class Price | |
{ | |
public string CurrencyCode { get; set; } | |
public int ListPrice { get; set; } | |
public int MSRP { get; set; } | |
} | |
public class OrderManagementData | |
{ | |
public Price Price { get; set; } | |
} | |
public class Availability | |
{ | |
public List<string> Actions { get; set; } | |
public string AvailabilityId { get; set; } | |
public Conditions Conditions { get; set; } | |
public OrderManagementData OrderManagementData { get; set; } | |
public int DisplayRank { get; set; } | |
public bool RemediationRequired { get; set; } | |
} | |
public class SkuExtendedJsonData | |
{ | |
public Sku Sku { get; set; } | |
public List<Availability> Availabilities { get; set; } | |
public static SkuExtendedJsonData GetFromJson(string json) | |
{ | |
return JsonSerializer.Deserialize<SkuExtendedJsonData>(json); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using System.Windows; | |
using System.Windows.Interop; | |
using Windows.Services.Store; | |
using Unclassified.TxLib; | |
using WindowTop.DataUnits; | |
using WindowTop.Helpers; | |
using WindowTop.Managers.ActivationManagers.UwpActivationManager.Models.SkuExtendedJsonData; | |
namespace WindowTop.Managers.ActivationManagers.UwpActivationManager | |
{ | |
public class UwpActivationManager : IActivationManager | |
{ | |
static readonly AppLogger log = new AppLogger(); | |
// TODO: This should match the store product id that defined in the Package.StoreAssociation.xml | |
const string STORE_PRODUCT_ID = "XXXXXXXXXXXX"; // Use your own product id here | |
const string STORE_SHORTCUT_LINK = "ms-windows-store://pdp/?ProductId=XXXXXXXXXXXX"; // Use your own product id here | |
const string STORE_PRO_ADDON_ID = "XXXXXXXXXXXX"; // Use your pro addon id here here | |
const string STORE_TRIAL_ADDON_ID = "XXXXXXXXXXXX"; // Use your trial addon id here here | |
public bool IsLicenseCached { get; private set; } | |
public bool IsFullVersion { get; private set; } | |
public bool IsTrialMode { get; private set; } | |
public int DaysLeft { get; private set; } | |
public int MaxDaysLeft => 30; | |
public bool IsFreeModeAllowed => true; | |
public DateTime? TrialStartDate { get; private set; } | |
public bool IsTrialModeAvailable { get; private set; } | |
public bool IsProductKeyMethod => false; | |
public bool SupportUnActivation => false; | |
public bool SupportResetActivation => false; | |
public Action<bool> OnActivationStateChanged { get; set; } | |
public UwpActivationManager() | |
{ | |
if (Cache.Values.UwpActivationCache.IsLicenseCached) | |
{ | |
IsTrialModeAvailable = Cache.Values.UwpActivationCache.IsTrialModeAvailable; | |
TrialStartDate = Cache.Values.UwpActivationCache.TrialStartDate; | |
IsTrialMode = Cache.Values.UwpActivationCache.IsTrialMode; | |
IsFullVersion = Cache.Values.UwpActivationCache.IsFullVersion; | |
DaysLeft = Cache.Values.UwpActivationCache.DaysLeft; | |
IsLicenseCached = true; | |
} | |
} | |
async Task StoreContextOnOfflineLicensesChangedAsync() | |
{ | |
var activationStateResult = await LoadActivationStateAsync(); | |
if (!activationStateResult.IsActivationLoaded) | |
log.Error( | |
$"Failed to refresh activation state on StoreContextOnOfflineLicensesChangedAsync event. Error {activationStateResult.LoadingFailureMessage}"); | |
} | |
void StoreContextOnOfflineLicensesChanged_EventHandler(StoreContext sender, object args) | |
{ | |
StoreContextOnOfflineLicensesChangedAsync().GetAwaiter().GetResult(); | |
} | |
static async Task<List<StoreProduct>> GetAllDurableAddonsAsync(StoreContext storeContext = null) | |
{ | |
if (storeContext == null) | |
{ | |
storeContext = UwpStoreContextManager.GetStoreContextForApp(IntPtr.Zero); | |
if (storeContext == null) | |
{ | |
log.Error("Failure in GetAllDurableAddonsAsync. Failed to get storeContext instance"); | |
return null; | |
} | |
} | |
var addonsRequest = await storeContext.GetAssociatedStoreProductsAsync(new[] {"Durable"}); | |
if (addonsRequest.ExtendedError != null) | |
{ | |
log.Error( | |
$"Failure in GetAllDurableAddonsAsync. Failed to get list of addons. Error: {addonsRequest.ExtendedError.Message}"); | |
return null; | |
} | |
if (addonsRequest.Products == null) | |
{ | |
log.Error("Failure in GetAllDurableAddonsAsync. No products returned"); | |
return null; | |
} | |
return addonsRequest.Products.Values.ToList(); | |
} | |
static async Task<bool> GetIsAppLegacyPurchasedAsync(StoreContext storeContext) | |
{ | |
var maxLegacyPurchaseDate = DateTime.ParseExact( | |
"2021-09-21", // Change this value as needed. From this date, the app become free and the full functionality moved to addon | |
"yyyy-MM-dd", | |
null) | |
.ToUniversalTime(); | |
var storeProductRequest = await storeContext.GetStoreProductForCurrentAppAsync(); | |
if (storeProductRequest.ExtendedError != null) | |
{ | |
log.Error( | |
$"Failure in GetIsAppLegacyPurchasedAsync() method. Error: {storeProductRequest.ExtendedError.Message}"); | |
return false; | |
} | |
var storeProduct = storeProductRequest.Product; | |
if (storeProduct == null) | |
{ | |
log.Error("Failure in GetIsAppLegacyPurchasedAsync(). storeProduct returned null"); | |
return false; | |
} | |
if (storeProduct.Skus == null) | |
{ | |
log.Error("Failure in GetIsAppLegacyPurchasedAsync(). Skus is null"); | |
return false; | |
} | |
foreach (var storeProductSku in storeProduct.Skus) | |
{ | |
SkuExtendedJsonData skuData; | |
try | |
{ | |
skuData = SkuExtendedJsonData.GetFromJson(storeProductSku.ExtendedJsonData); | |
} | |
catch (Exception e) | |
{ | |
log.Error( | |
$"Failure in GetIsAppLegacyPurchasedAsync(). Failed to parse skuData. Error: {e.Message}"); | |
continue; | |
} | |
if (skuData.Sku == null) | |
continue; | |
if (skuData.Sku.CollectionData == null) | |
continue; | |
var cd = skuData.Sku.CollectionData; | |
var isLegacyPurchase = | |
cd.productId == STORE_PRODUCT_ID && | |
cd.skuType == "Full" && | |
cd.status == "Active" && | |
cd.acquiredDate <= maxLegacyPurchaseDate; | |
if (isLegacyPurchase) | |
return true; | |
} | |
return false; | |
} | |
static DateTime? GetTrialStartDate(StoreProduct trialAddon) | |
{ | |
if (trialAddon.Skus == null) | |
{ | |
log.Error("Failure in GetTrialStartDate. trialAddon.Skus is null"); | |
return null; | |
} | |
DateTime? firstTimeDate = null; | |
foreach (var trialAddonSku in trialAddon.Skus) | |
{ | |
SkuExtendedJsonData skuData; | |
try | |
{ | |
skuData = SkuExtendedJsonData.GetFromJson(trialAddonSku.ExtendedJsonData); | |
} | |
catch (Exception e) | |
{ | |
log.Error( | |
$"Failure in GetTrialStartDate(). Failed to parse skuData. Error: {e.Message}"); | |
continue; | |
} | |
if (skuData.Sku?.CollectionData == null) | |
continue; | |
var cd = skuData.Sku.CollectionData; | |
if ( | |
cd.productId == trialAddon.StoreId && | |
cd.skuType == "Full" && | |
cd.status == "Active" && | |
(firstTimeDate == null || cd.acquiredDate < firstTimeDate)) | |
{ | |
firstTimeDate = cd.acquiredDate; | |
} | |
} | |
if (firstTimeDate == null) | |
{ | |
log.Error("Failure in GetTrialStartDate(). firstTimeDate is null"); | |
return null; | |
} | |
return firstTimeDate; | |
} | |
async Task<bool> CalculateTrialModeAsync(StoreProduct trialAddon = null) | |
{ | |
if (trialAddon == null) | |
{ | |
var allAddons = await GetAllDurableAddonsAsync(); | |
if (allAddons == null) | |
{ | |
log.Error("Failure in CalculateTrialModeAsync. allAddons returned as null"); | |
return false; | |
} | |
trialAddon = allAddons.FirstOrDefault(a => a.StoreId == STORE_TRIAL_ADDON_ID); | |
} | |
if (trialAddon == null) | |
{ | |
log.Error("Failure in CalculateTrialModeAsync. trialAddon is null"); | |
return false; | |
} | |
if (trialAddon.IsInUserCollection) | |
{ | |
log.Info("Trial addon is in the user collection"); | |
var trialStartDate = GetTrialStartDate(trialAddon); | |
if (trialStartDate == null) | |
{ | |
log.Error( | |
$"Failed to int daysLeft property from trialAddon."); | |
return false; | |
} | |
TrialStartDate = trialStartDate; | |
IsTrialModeAvailable = false; | |
UpdateTrailMode(); | |
if (!IsTrialMode) | |
log.Info( | |
$"Trial time already passed. So it is disabled, and IsFullVersion property is: {IsFullVersion}"); | |
} | |
else | |
{ | |
log.Info("Trial addon is not in the user collection"); | |
IsTrialMode = false; | |
IsTrialModeAvailable = true; | |
} | |
return true; | |
} | |
public async Task<LoadActivationStateResult> LoadActivationStateAsync() | |
{ | |
var o = new LoadActivationStateResult(); | |
// Get the store context | |
var storeContext = UwpStoreContextManager.GetStoreContextForApp(IntPtr.Zero); | |
if (storeContext == null) | |
{ | |
log.Error( | |
"Failure in LoadActivationStateAsync. Failed to get storeContext object in UwpActivationManager"); | |
o.LoadingFailureMessage = log.LastLog; | |
return o; | |
} | |
// Set up OnOfflineLicensesChanged_EventHandler only once (make sure we added it once) | |
storeContext.OfflineLicensesChanged -= StoreContextOnOfflineLicensesChanged_EventHandler; | |
storeContext.OfflineLicensesChanged += StoreContextOnOfflineLicensesChanged_EventHandler; | |
var oldIsFullVersion = IsFullVersion; | |
var oldIsTrialMode = IsTrialMode; | |
if (await GetIsAppLegacyPurchasedAsync(storeContext)) | |
{ | |
log.Info("Detected legacy puchase"); | |
IsFullVersion = true; | |
IsTrialMode = false; | |
} | |
else | |
{ | |
log.Info("No legacy purchase detected"); | |
var allAddons = await GetAllDurableAddonsAsync(storeContext); | |
if (allAddons == null) | |
{ | |
log.Error("Failure in LoadActivationStateAsync. Failed to get addons list. it returned as null"); | |
o.LoadingFailureMessage = log.LastLog; | |
return o; | |
} | |
var proAddon = allAddons.FirstOrDefault(a => a.StoreId == STORE_PRO_ADDON_ID); | |
if (proAddon == null) | |
{ | |
log.Error("Failure in LoadActivationStateAsync. Failed to get proAddon. It returned as null"); | |
o.LoadingFailureMessage = log.LastLog; | |
return o; | |
} | |
if (proAddon.IsInUserCollection) | |
{ | |
log.Info("Pro addon is in the user collection"); | |
IsFullVersion = true; | |
IsTrialMode = false; | |
IsTrialModeAvailable = false; | |
} | |
else | |
{ | |
log.Info("Pro addon is not in the user collection"); | |
var trialAddon = allAddons.FirstOrDefault(a => a.StoreId == STORE_TRIAL_ADDON_ID); | |
if (trialAddon == null) | |
{ | |
log.Error("Failure in LoadActivationStateAsync. Failed to get trialAddon. It returned as null"); | |
o.LoadingFailureMessage = log.LastLog; | |
return o; | |
} | |
if (!await CalculateTrialModeAsync(trialAddon)) | |
{ | |
log.Error( | |
"Failure in LoadActivationStateAsync. There is failure in CalculateTrialModeAsync method."); | |
o.LoadingFailureMessage = log.LastLog; | |
return o; | |
} | |
IsFullVersion = IsTrialMode; | |
} | |
} | |
// // TODO: This is for testing only! Remove this later!!! | |
// IsTrialMode = true; | |
// IsFullVersion = true; | |
// SaveLicenseToCache(); | |
// // ENDOF TODO | |
SaveLicenseToCache(); | |
o.IsActivationLoaded = true; | |
o.IsActivationStateChanged = oldIsFullVersion != IsFullVersion || oldIsTrialMode != IsTrialMode; | |
log.Info( | |
$"License state: IsFullVersion={IsFullVersion}, IsTrialMode={IsTrialMode}, IsActivationStateChanged={o.IsActivationStateChanged}"); | |
return o; | |
} | |
public void OpenPurchasePage() | |
{ | |
Process.Start(STORE_SHORTCUT_LINK); | |
} | |
public async Task<Result<bool>> RequestSetTrialModeAsync(Window ownerWindow = null) | |
{ | |
var result = await RequestGetProductAsync(STORE_TRIAL_ADDON_ID, ownerWindow); | |
if (result.IsFailed) | |
{ | |
log.Error( | |
$"Failure in RequestSetTrialModeAsync. Basic failure in RequestGetProductAsync. Error: {result.ErrorMessage}"); | |
return new Result<bool>(log.LastLog, false); | |
} | |
if (result.Value.ExtendedError != null) | |
{ | |
log.Error( | |
$"Failure in RequestSetTrialModeAsync. API failure in RequestGetProductAsync. Error: {result.Value.ExtendedError.Message}"); | |
return new Result<bool>(log.LastLog, false); | |
} | |
var requestResult = result.Value; | |
log.Info($"requestResult.Status={requestResult.Status}"); | |
async Task HandleTrialModeOwnedEvent() | |
{ | |
var oldIsTrialMode = IsTrialMode; | |
await CalculateTrialModeAsync(); | |
if (IsTrialMode) | |
IsFullVersion = IsTrialMode; | |
SaveLicenseToCache(); | |
OnActivationStateChanged?.Invoke(oldIsTrialMode != IsTrialMode); | |
} | |
switch (requestResult.Status) | |
{ | |
case StorePurchaseStatus.AlreadyPurchased: | |
{ | |
App.ToastManager.ShowError(Tx.T("The trial period already started for this product")); | |
await HandleTrialModeOwnedEvent(); | |
return new Result<bool>(true); | |
} | |
case StorePurchaseStatus.Succeeded: | |
{ | |
await HandleTrialModeOwnedEvent(); | |
return new Result<bool>(true); | |
} | |
case StorePurchaseStatus.NotPurchased: | |
{ | |
App.ToastManager.ShowNotification( | |
Tx.T("The trial period was not started_ it may have been canceled")); | |
return new Result<bool>(true); | |
} | |
case StorePurchaseStatus.NetworkError: | |
{ | |
var failureReason = Tx.T("The trial period was not started due to a Network Error"); | |
App.ToastManager.ShowError(failureReason); | |
return new Result<bool>(failureReason, false); | |
} | |
case StorePurchaseStatus.ServerError: | |
{ | |
var failureReason = Tx.T("The trial period was not started due to a Server Error"); | |
App.ToastManager.ShowError(failureReason); | |
return new Result<bool>(failureReason, false); | |
} | |
default: | |
{ | |
var failureReason = Tx.T("The trial period was not started due to an Unknown Error"); | |
App.ToastManager.ShowError(failureReason); | |
return new Result<bool>(failureReason, false); | |
} | |
} | |
// throw new NotImplementedException( | |
// "In UWP you never should be able to set trial mode from within the software." + | |
// "If you got this error, it is bug, contact the developer for support"); | |
} | |
public bool UpdateTrailMode() | |
{ | |
if (TrialStartDate == null) | |
{ | |
log.Error("Can't calculate days left because the TrialStartDate is null"); | |
return false; | |
} | |
var daysPassed = (DateTime.Now - TrialStartDate).Value.Days; | |
DaysLeft = MaxDaysLeft - daysPassed; | |
log.Info($"Calculated trial days. daysPassed={daysPassed}, daysLeft={DaysLeft}"); | |
var oldIsTrialMode = IsTrialMode; | |
IsTrialMode = DaysLeft > 0; | |
IsFullVersion = IsTrialMode; | |
return oldIsTrialMode != IsTrialMode; | |
} | |
static async Task<Result<StorePurchaseResult>> RequestGetProductAsync(string productId, Window ownerWindow = null) | |
{ | |
var ownerWindowPtr = IntPtr.Zero; | |
if (ownerWindow != null) | |
ownerWindowPtr = new WindowInteropHelper(ownerWindow).EnsureHandle(); | |
var storeContext = UwpStoreContextManager.GetStoreContextForApp(ownerWindowPtr); | |
if (storeContext == null) | |
{ | |
log.Error("Failed to get storeContext object in UwpActivationManager"); | |
return new Result<StorePurchaseResult>(log.LastLog, null); | |
} | |
var result = await storeContext.RequestPurchaseAsync(productId); | |
return new Result<StorePurchaseResult>(result); | |
} | |
public async Task<Result<bool>> RequestPurchaseAppAsync(Window ownerWindow = null) | |
{ | |
var requestProduct = await RequestGetProductAsync(STORE_PRO_ADDON_ID, ownerWindow); | |
if (requestProduct.IsFailed) | |
{ | |
log.Error($"Failed in RequestPurchaseAppAsync. Reason: {requestProduct.ErrorMessage}"); | |
return new Result<bool>(log.LastLog, false); | |
} | |
var requestPurchaseResult = requestProduct.Value; | |
if (requestPurchaseResult.ExtendedError != null) | |
{ | |
log.Error($"Failed in RequestPurchaseAsync. Error: {requestPurchaseResult.ExtendedError}"); | |
return new Result<bool>(log.LastLog, false); | |
} | |
var oldIsFullVersion = IsFullVersion; | |
var oldIsTrialMode = IsTrialMode; | |
log.Info($"StorePurchaseStatus={requestPurchaseResult.Status}"); | |
// License will refresh automatically using the StoreContext.OfflineLicensesChanged event | |
switch (requestPurchaseResult.Status) | |
{ | |
case StorePurchaseStatus.AlreadyPurchased: | |
{ | |
App.ToastManager.ShowError(Tx.T("TEXT_You already bought this app")); | |
IsFullVersion = true; | |
IsTrialMode = false; | |
SaveLicenseToCache(); | |
OnActivationStateChanged?.Invoke(oldIsFullVersion != IsFullVersion || | |
oldIsTrialMode != IsTrialMode); | |
return new Result<bool>(true); | |
} | |
case StorePurchaseStatus.Succeeded: | |
{ | |
// License will refresh automatically using the StoreContext.OfflineLicensesChanged event | |
App.ToastManager.ShowSuccess(Tx.T("TEXT_Your purchase was successful")); | |
IsFullVersion = true; | |
IsTrialMode = false; | |
SaveLicenseToCache(); | |
OnActivationStateChanged?.Invoke(oldIsFullVersion != IsFullVersion || | |
oldIsTrialMode != IsTrialMode); | |
return new Result<bool>(true); | |
} | |
case StorePurchaseStatus.NotPurchased: | |
{ | |
App.ToastManager.ShowNotification( | |
Tx.T("Product was not purchased_ it may have been canceled")); | |
return new Result<bool>(false); | |
} | |
case StorePurchaseStatus.NetworkError: | |
{ | |
var failureReason = Tx.T("Product was not purchased due to a Network Error"); | |
App.ToastManager.ShowError(failureReason); | |
return new Result<bool>(failureReason, false); | |
} | |
case StorePurchaseStatus.ServerError: | |
{ | |
var failureReason = Tx.T("Product was not purchased due to a Server Error"); | |
App.ToastManager.ShowError(failureReason); | |
return new Result<bool>(failureReason, false); | |
} | |
default: | |
{ | |
var failureReason = Tx.T("Product was not purchased due to an Unknown Error"); | |
App.ToastManager.ShowError(failureReason); | |
return new Result<bool>(failureReason, false); | |
} | |
} | |
} | |
public void RequestActivateProductKey(string productKey, Action onActivated, | |
Action<string> onFailActivate, Action<string> onIllegalActivation) | |
{ | |
throw new NotImplementedException( | |
"This method (RequestActivateProductKey) should not be called in the current IActivationManager implementation" + | |
"because IsProductKeyMethod property is false. This is the UWP implementation of IActivationManager." + | |
"If you got this error, it is bug. contact the developer for support"); | |
} | |
public void RequestDeActivate(Action onUnActivated, Action<string> onUnActivateFailed) | |
{ | |
throw new NotImplementedException( | |
"This method (RequestDeActivate) should not be called in the current IActivationManager implementation" + | |
"because SupportUnActivation property is false."); | |
} | |
public void RequestCleanActivateProductKey(string productKey, Action onSuccess, Action<string> onFailure) | |
{ | |
throw new NotImplementedException( | |
"This method (RequestCleanActivateProductKey) should not be called in the current IActivationManager implementation" + | |
"because SupportResetActivation property is false."); | |
} | |
void SaveLicenseToCache() | |
{ | |
Cache.Values.UwpActivationCache.IsTrialModeAvailable = IsTrialModeAvailable; | |
Cache.Values.UwpActivationCache.TrialStartDate = TrialStartDate; | |
Cache.Values.UwpActivationCache.IsTrialMode = IsTrialMode; | |
Cache.Values.UwpActivationCache.IsFullVersion = IsFullVersion; | |
Cache.Values.UwpActivationCache.DaysLeft = DaysLeft; | |
Cache.Values.UwpActivationCache.IsLicenseCached = true; | |
IsLicenseCached = true; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.InteropServices; | |
using System.Windows; | |
using System.Windows.Interop; | |
using Windows.Services.Store; | |
using NLog; | |
namespace WindowTop.Managers | |
{ | |
public class UwpStoreContextManager | |
{ | |
static readonly Logger log = LogManager.GetCurrentClassLogger(); | |
static IntPtr contextWinHandle = IntPtr.Zero; | |
public static StoreContext GetStoreContextForApp(IntPtr ownerWindow) | |
{ | |
try | |
{ | |
var storeContext = StoreContext.GetDefault(); | |
var initWindow = (IInitializeWithWindow) (object) storeContext; | |
if (ownerWindow == IntPtr.Zero) | |
{ | |
if (contextWinHandle == IntPtr.Zero) | |
{ | |
contextWinHandle = new WindowInteropHelper(new Window() | |
{Topmost = true, WindowStartupLocation = WindowStartupLocation.CenterScreen}) | |
.EnsureHandle(); | |
} | |
ownerWindow = contextWinHandle; | |
} | |
initWindow.Initialize(ownerWindow); | |
return storeContext; | |
} | |
catch (Exception e) | |
{ | |
// error = new LauncherException("An error has occured while loading the license information from Microsoft Store"); | |
log.Error( | |
$"An error has occured while loading the license information from Microsoft Store. Exception: {e.Message}"); | |
return null; | |
} | |
} | |
[ComImport] | |
[Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")] | |
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
public interface IInitializeWithWindow | |
{ | |
void Initialize(IntPtr hwnd); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment