193 lines
6.7 KiB
C#
193 lines
6.7 KiB
C#
namespace LMEMappeCatastaliApp.Services;
|
|
|
|
public sealed class EntitlementService(
|
|
ManualLicenseService manualLicenses,
|
|
IPlayBillingService playBilling,
|
|
PlayPurchaseVerificationService purchaseVerification)
|
|
{
|
|
public const string ProductId = "lme_pro";
|
|
public const string MonthlyBasePlanId = "monthly";
|
|
public const string YearlyBasePlanId = "yearly";
|
|
public const int BaseDrawingObjectLimit = 15;
|
|
|
|
private const string GoogleProActivePreference = "entitlement_google_pro_active";
|
|
private const string GoogleExpiresAtPreference = "entitlement_google_pro_expires_at";
|
|
private const string GoogleLastVerifiedAtPreference = "entitlement_google_pro_last_verified_at";
|
|
private static readonly TimeSpan OfflineGracePeriod = TimeSpan.FromDays(7);
|
|
private static readonly HashSet<string> ProFeatures = new(StringComparer.Ordinal)
|
|
{
|
|
AppFeatures.ProjectArchive,
|
|
AppFeatures.Export,
|
|
AppFeatures.CloudSync,
|
|
AppFeatures.OfflineMaps,
|
|
AppFeatures.AdvancedGeometry,
|
|
AppFeatures.GpsTrail,
|
|
AppFeatures.FiducialPoints,
|
|
AppFeatures.MultipleNotes
|
|
};
|
|
|
|
public bool IsGoogleProActive { get; private set; }
|
|
public DateTimeOffset? GoogleExpiresAt { get; private set; }
|
|
public DateTimeOffset? GoogleLastVerifiedAt { get; private set; }
|
|
public ManualLicenseInfo? ManualLicense => manualLicenses.Current;
|
|
public string? ManualLicenseError => manualLicenses.LastError;
|
|
|
|
public bool HasGoogleProAccess
|
|
{
|
|
get
|
|
{
|
|
var now = DateTimeOffset.UtcNow;
|
|
if (!IsGoogleProActive || GoogleExpiresAt is { } expiresAt && expiresAt <= now)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return GoogleLastVerifiedAt is null
|
|
|| now - GoogleLastVerifiedAt <= OfflineGracePeriod;
|
|
}
|
|
}
|
|
|
|
public bool HasManualProAccess => manualLicenses.HasActiveLicense;
|
|
public bool HasProAccess => HasGoogleProAccess || HasManualProAccess;
|
|
|
|
public string PlanLabel => HasProAccess
|
|
? HasGoogleProAccess && HasManualProAccess ? "LME Pro (Google + manuale)"
|
|
: HasGoogleProAccess ? "LME Pro (Google Play)"
|
|
: "LME Pro (licenza manuale)"
|
|
: "Base";
|
|
|
|
public bool RequiresPro(string feature) => ProFeatures.Contains(feature);
|
|
public bool CanUse(string feature) => !RequiresPro(feature) || HasProAccess;
|
|
|
|
public Task<IReadOnlyList<PlaySubscriptionPlan>> GetSubscriptionPlansAsync(
|
|
CancellationToken cancellationToken = default) =>
|
|
playBilling.GetSubscriptionPlansAsync(cancellationToken);
|
|
|
|
public async Task LoadAsync()
|
|
{
|
|
IsGoogleProActive = Preferences.Default.Get(GoogleProActivePreference, false);
|
|
GoogleExpiresAt = ReadDate(GoogleExpiresAtPreference);
|
|
GoogleLastVerifiedAt = ReadDate(GoogleLastVerifiedAtPreference);
|
|
await manualLicenses.LoadAsync();
|
|
}
|
|
|
|
public void ApplyVerifiedGooglePro(DateTimeOffset? expiresAt)
|
|
{
|
|
IsGoogleProActive = true;
|
|
GoogleExpiresAt = expiresAt;
|
|
GoogleLastVerifiedAt = DateTimeOffset.UtcNow;
|
|
SaveGoogleState();
|
|
}
|
|
|
|
public void ApplyGoogleBase()
|
|
{
|
|
IsGoogleProActive = false;
|
|
GoogleExpiresAt = null;
|
|
GoogleLastVerifiedAt = DateTimeOffset.UtcNow;
|
|
SaveGoogleState();
|
|
}
|
|
|
|
public Task<ManualLicenseActivationResult> ActivateManualLicenseAsync(string? token) =>
|
|
manualLicenses.ActivateAsync(token);
|
|
|
|
public Task RemoveManualLicenseAsync() => manualLicenses.RemoveAsync();
|
|
|
|
public async Task<PlayBillingResult> RestorePurchasesAsync()
|
|
{
|
|
var result = await playBilling.RestoreAsync();
|
|
return await ApplyPlayBillingResultAsync(result);
|
|
}
|
|
|
|
public async Task<PlayBillingResult> PurchaseGoogleProAsync(string basePlanId)
|
|
{
|
|
var result = await playBilling.PurchaseAsync(basePlanId);
|
|
return await ApplyPlayBillingResultAsync(result);
|
|
}
|
|
|
|
private async Task<PlayBillingResult> ApplyPlayBillingResultAsync(PlayBillingResult result)
|
|
{
|
|
if (!result.Completed)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
if (result.HasActiveSubscription)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(result.PurchaseToken))
|
|
{
|
|
var verification = await purchaseVerification.VerifyAsync(result.PurchaseToken);
|
|
if (verification.Verified)
|
|
{
|
|
if (verification.Active)
|
|
{
|
|
ApplyVerifiedGooglePro(verification.ExpiresAt);
|
|
return result with
|
|
{
|
|
Message = string.IsNullOrWhiteSpace(verification.Message)
|
|
? result.Message
|
|
: verification.Message
|
|
};
|
|
}
|
|
|
|
ApplyGoogleBase();
|
|
return PlayBillingResult.Base(string.IsNullOrWhiteSpace(verification.Message)
|
|
? "L'abbonamento Google Play non risulta attivo."
|
|
: verification.Message);
|
|
}
|
|
|
|
if (purchaseVerification.IsRequired)
|
|
{
|
|
return PlayBillingResult.Failed(verification.Message);
|
|
}
|
|
}
|
|
|
|
ApplyVerifiedGooglePro(null);
|
|
}
|
|
else
|
|
{
|
|
ApplyGoogleBase();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void SaveGoogleState()
|
|
{
|
|
Preferences.Default.Set(GoogleProActivePreference, IsGoogleProActive);
|
|
WriteDate(GoogleExpiresAtPreference, GoogleExpiresAt);
|
|
WriteDate(GoogleLastVerifiedAtPreference, GoogleLastVerifiedAt);
|
|
}
|
|
|
|
private static DateTimeOffset? ReadDate(string key)
|
|
{
|
|
var value = Preferences.Default.Get(key, string.Empty);
|
|
return DateTimeOffset.TryParse(value, out var parsed) ? parsed : null;
|
|
}
|
|
|
|
private static void WriteDate(string key, DateTimeOffset? value)
|
|
{
|
|
if (value is null)
|
|
{
|
|
Preferences.Default.Remove(key);
|
|
return;
|
|
}
|
|
|
|
Preferences.Default.Set(key, value.Value.ToString("O"));
|
|
}
|
|
}
|
|
|
|
public static class AppFeatures
|
|
{
|
|
public const string CadastreMap = "cadastre-map";
|
|
public const string BasicSearch = "basic-search";
|
|
public const string DrawingMeasurements = "drawing-measurements";
|
|
public const string ProjectArchive = "project-archive";
|
|
public const string Export = "export";
|
|
public const string CloudSync = "cloud-sync";
|
|
public const string OfflineMaps = "offline-maps";
|
|
public const string AdvancedGeometry = "advanced-geometry";
|
|
public const string GpsTrail = "gps-trail";
|
|
public const string FiducialPoints = "fiducial-points";
|
|
public const string MultipleNotes = "multiple-notes";
|
|
}
|